﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using Tesla.Content;
using Tesla.Core;
using Tesla.Graphics;
using Tesla.Math;

namespace Tesla.Scene.Shape {
    /// <summary>
    /// A mesh that represents a 3D cube with 6 faces. A box has indexed primitives
    /// with each vertex composed of a Position, Normal, and Texture Coordinate by default. The box
    /// is aligned with the standard axes by default.
    /// </summary>
    public class Box : Mesh {
        private Vector3 _extents;
        private Vector3 _xAxis = new Vector3(1, 0, 0);
        private Vector3 _yAxis = new Vector3(0, 1, 0);
        private Vector3 _zAxis = new Vector3(0, 0, 1);
        private Vector3 _center;

        private Vector3[] _exAxes;
        private Vector3[] _vertices;
        private bool _constructed;
        private bool _insideOut;

        /// <summary>
        /// Gets the extents along the X, Y, and Z axes.
        /// </summary>
        public Vector3 Extents {
            get {
                return _extents;
            }
        }

        /// <summary>
        /// Gets the center of the box.
        /// </summary>
        public Vector3 Center {
            get {
                return _center;
            }
        }

        //For ISavable
        private Box() {}

        /// <summary>
        /// Creates a new instance of a box centered at the origin.
        /// </summary>
        /// <param name="name">Name of the box</param>
        /// <param name="xExtent">xExtent (half length) along the x-axis</param>
        /// <param name="yExtent">yExtent (half length) along the y-axis</param>
        /// <param name="zExtent">zExtent (half length) along the z-axis</param>
        public Box(String name, float xExtent, float yExtent, float zExtent)
            : base(name) {
                SetData(Vector3.Zero, new Vector3(xExtent, yExtent, zExtent), false);
        }

        /// <summary>
        /// Creates a new instance of Box.
        /// </summary>
        /// <param name="name">Name of the box</param>
        /// <param name="center">Center of the box</param>
        /// <param name="xExtent">xExtent (half length) along the x-axis</param>
        /// <param name="yExtent">yExtent (half length) along the y-axis</param>
        /// <param name="zExtent">zExtent (half length) along the z-axis</param>
        public Box(String name, Vector3 center, float xExtent, float yExtent, float zExtent)
            : base(name) {
                SetData(center, new Vector3(xExtent, yExtent, zExtent), false);
        }

        /// <summary>
        /// Creates a new instance of Box.
        /// </summary>
        /// <param name="name">Name of the box</param>
        /// <param name="center">Center of the box</param>
        /// <param name="xExtent">xExtent (half length) along the x-axis</param>
        /// <param name="yExtent">yExtent (half length) along the y-axis</param>
        /// <param name="zExtent">zExtent (half length) along the z-axis</param>
        /// <param name="insideOut">Create an inside-out box (swaps winding order)</param>
        public Box(String name, Vector3 center, float xExtent, float yExtent, float zExtent, bool insideOut)
            : base(name) {
                SetData(center, new Vector3(xExtent, yExtent, zExtent), insideOut);
        }

        /// <summary>
        /// Update's the box's data with the new center and extents (half-lengths). 
        /// </summary>
        /// <param name="center">Center of the box</param>
        /// <param name="xExtent">xExtent (half-length) along the x-axis</param>
        /// <param name="yExtent">yExtent (half-length) along the y-axis</param>
        /// <param name="zExtent">zExtent (half-length) along the z-axis</param>
        public void SetData(Vector3 center, float xExtent, float yExtent, float zExtent) {
            SetData(center, new Vector3(xExtent, yExtent, zExtent), false);
        }

        /// <summary>
        /// Update's the box's data with the new center and extents (half-lengths). 
        /// </summary>
        /// <param name="center">Center of the box</param>
        /// <param name="xExtent">xExtent (half-length) along the x-axis</param>
        /// <param name="yExtent">yExtent (half-length) along the y-axis</param>
        /// <param name="zExtent">zExtent (half-length) along the z-axis</param>
        /// <param name="insideOut">Create an inside-out box (swaps winding order)</param>
        public void SetData(Vector3 center, float xExtent, float yExtent, float zExtent, bool insideOut) {
            SetData(center, new Vector3(xExtent, yExtent, zExtent), insideOut);
        }

        /// <summary>
        /// Update's the box's data with the new center and extents (half-lengths).
        /// </summary>
        /// <param name="center">Center of the box</param>
        /// <param name="extents">Box extents (half-lengths) along each axis</param>
        public void SetData(Vector3 center, Vector3 extents) {
            SetData(center, extents, false);
        }

        /// <summary>
        /// Update's the box's data with the new center and extents (half-lengths).
        /// </summary>
        /// <param name="center">Center of the box</param>
        /// <param name="extents">Box extents (half-lengths) along each axis</param>
        /// <param name="insideOut">Create an inside-out box (swaps winding order)</param>
        public void SetData(Vector3 center, Vector3 extents, bool insideOut) {
            _center = center;
            _extents = extents;

            bool reconstruct = false;

            reconstruct |= SetPositionData();

            if(!_constructed || insideOut != _insideOut) {
                SetNormalData(insideOut);
                SetTextureData(insideOut);
                SetIndexData(insideOut);
            }

            if(reconstruct || MeshData.VertexBufferDirty || MeshData.IndexBufferDirty) {
                MeshData.Reconstruct();
            }

            UpdateModelBound();
            _insideOut = insideOut;
            _constructed = true;
        }

        /// <summary>
        /// Sets the axes of the Box. SetData() must be called for the data to be recomputed.
        /// </summary>
        /// <param name="xAxis">X-Axis normalized</param>
        /// <param name="yAxis">Y-Axis normalized</param>
        /// <param name="zAxis">Z-axis normalized</param>
        public void SetAxes(Vector3 xAxis, Vector3 yAxis, Vector3 zAxis) {
            _xAxis = xAxis;
            _yAxis = yAxis;
            _zAxis = zAxis;
        }

        private void ComputeVertices() {
            //Setup data if null
            if(_exAxes == null) {
                _exAxes = new Vector3[3];
            }
            if(_vertices == null) {
                _vertices = new Vector3[8];
            }

            //Multiply axes by the extents
            _exAxes[0] = _xAxis * _extents.X;
            _exAxes[1] = _yAxis * _extents.Y;
            _exAxes[2] = _zAxis * _extents.Z;

            //Use the scaled axes to compute the 8 corners
            _vertices[0] = ((_center - _exAxes[0]) + _exAxes[1]) + _exAxes[2];
            _vertices[1] = ((_center + _exAxes[0]) + _exAxes[1]) + _exAxes[2];
            _vertices[2] = ((_center + _exAxes[0]) - _exAxes[1]) + _exAxes[2];
            _vertices[3] = ((_center - _exAxes[0]) - _exAxes[1]) + _exAxes[2];
            

            _vertices[4] = ((_center + _exAxes[0]) + _exAxes[1]) - _exAxes[2];
            _vertices[5] = ((_center - _exAxes[0]) + _exAxes[1]) - _exAxes[2];
            _vertices[6] = ((_center - _exAxes[0]) - _exAxes[1]) - _exAxes[2];
            _vertices[7] = ((_center + _exAxes[0]) - _exAxes[1]) - _exAxes[2];
        }

        private bool SetPositionData() {
            ComputeVertices();

            DataBuffer<Vector3> positions = MeshData.Positions;
            bool flag = false;
            if(positions == null) {
                positions = new DataBuffer<Vector3>(24);
                flag = true;
            } else {
                positions.Position = 0;
            }
            //Front
            positions.Set(_vertices[0]);
            positions.Set(_vertices[1]);
            positions.Set(_vertices[2]);
            positions.Set(_vertices[3]);

            //Back
            positions.Set(_vertices[4]);
            positions.Set(_vertices[5]);
            positions.Set(_vertices[6]);
            positions.Set( _vertices[7]);

            //Left
            positions.Set(_vertices[5]);
            positions.Set(_vertices[0]);
            positions.Set(_vertices[3]);
            positions.Set(_vertices[6]);

            //Right
            positions.Set(_vertices[1]);
            positions.Set(_vertices[4]);
            positions.Set(_vertices[7]);
            positions.Set(_vertices[2]);

            //Top
            positions.Set(_vertices[5]);
            positions.Set(_vertices[4]);
            positions.Set(_vertices[1]);
            positions.Set(_vertices[0]);

            //Bottom
            positions.Set(_vertices[7]);
            positions.Set(_vertices[6]);
            positions.Set(_vertices[3]);
            positions.Set(_vertices[2]);

            MeshData.UpdateVertexData<Vector3>(VertexSemantic.Position, positions);
            return flag;
        }

        private void SetNormalData(bool flip) {
            DataBuffer<Vector3> normals = MeshData.Normals;
            if(normals == null) {
                normals = new DataBuffer<Vector3>(24);
            } else {
                normals.Position = 0;
            }

            //Front
            for(int i = 0; i < 4; i++) {
                if(flip) {
                    normals.Set(new Vector3(0, 0, -1));
                } else {
                    normals.Set(new Vector3(0, 0, 1));
                }
            }
            //Back
            for(int i = 4; i < 8; i++) {
                if(flip) {
                    normals.Set(new Vector3(0, 0, 1));
                } else {
                    normals.Set(new Vector3(0, 0, -1));
                }
            }
            //Left
            for(int i = 8; i < 12; i++) {
                if(flip) {
                    normals.Set(new Vector3(1, 0, 0));
                } else {
                    normals.Set(new Vector3(-1, 0, 0));
                }
            }
            //Right
            for(int i = 12; i < 16; i++) {
                if(flip) {
                    normals.Set(new Vector3(-1, 0, 0));
                } else {
                    normals.Set(new Vector3(1, 0, 0));
                }
            }
            //Top
            for(int i = 16; i < 20; i++) {
                if(flip) {
                    normals.Set(new Vector3(0, -1, 0));
                } else {
                    normals.Set(new Vector3(0, 1, 0));
                }
            }
            //Bottom
            for(int i = 20; i < 24; i++) {
                if(flip) {
                    normals.Set(new Vector3(0, 1, 0));
                } else {
                    normals.Set(new Vector3(0, -1, 0));
                }
            }
            MeshData.UpdateVertexData<Vector3>(VertexSemantic.Normal, normals);
        }

        private void SetTextureData(bool swap) {
            DataBuffer<Vector2> texCoords = MeshData.TextureCoordinates;
            if(texCoords == null) {
                texCoords = new DataBuffer<Vector2>(24);
            } else {
                texCoords.Position = 0;
            }

            for(int i = 0; i < 6; i++) {
                if(swap) {
                    texCoords.Set(new Vector2(1, 0));
                    texCoords.Set(new Vector2(0, 0));
                    texCoords.Set(new Vector2(0, 1));
                    texCoords.Set(new Vector2(1, 1));
                } else {
                    texCoords.Set(new Vector2(0, 0));
                    texCoords.Set(new Vector2(1, 0));
                    texCoords.Set(new Vector2(1, 1));
                    texCoords.Set(new Vector2(0, 1));
                }
            }
            MeshData.UpdateVertexData<Vector2>(VertexSemantic.TextureCoordinate, texCoords);
        }

        private void SetIndexData(bool swap) {
            DataBuffer<short> indices = MeshData.Indices as DataBuffer<short>;
            if(indices == null) {
                indices = new DataBuffer<short>(36);
            } else {
                indices.Position = 0;
            }
            if(swap) {
                for(short i = 0; i < 24; i += 4) {
                    indices.Set((short) (i + 2));
                    indices.Set((short) (i + 1));
                    indices.Set(i);

                    indices.Set((short) (i + 3));
                    indices.Set((short) (i + 2));
                    indices.Set(i);
                }
            } else {
                for(short i = 0; i < 24; i += 4) {
                    indices.Set(i);
                    indices.Set((short) (i + 1));
                    indices.Set((short) (i + 2));

                    indices.Set(i);
                    indices.Set((short) (i + 2));
                    indices.Set((short) (i + 3));
                }
            }
            MeshData.UpdateIndexData<short>(indices);
        }

        /// <summary>
        /// Serializes this Mesh.
        /// </summary>
        /// <param name="output">Output to be written to</param>
        public override void Write(ISavableWriter output) {
            base.Write(output);
            output.Write("Extents", _extents);
            output.Write("Center", _center);
            output.Write("XAxis", _xAxis);
            output.Write("YAxis", _yAxis);
            output.Write("ZAxis", _zAxis);
            output.Write("InsideOut", _insideOut);
        }

        /// <summary>
        /// Deserializes this Mesh.
        /// </summary>
        /// <param name="input">Input to read from</param>
        public override void Read(ISavableReader input) {
            base.Read(input);
            _extents = input.ReadVector3();
            _center = input.ReadVector3();
            _xAxis = input.ReadVector3();
            _yAxis = input.ReadVector3();
            _zAxis = input.ReadVector3();
            _insideOut = input.ReadBoolean();
            _constructed = true;
        }
    }
}
